﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using FNN.CustomFunctions;
using FNN.NetworkComponents;
using FNN.Genetic;

namespace FNN.Networks
{
    [Serializable]
    public class FinancialNetwork : Network
    {
        protected double FundsValue = 100000;
        protected double StockValue;

        /// <summary>
        ///     Конструирование сети с заданной активационной функцией
        /// </summary>
        /// <param name="inputCount">Количество входных нейронов</param>
        /// <param name="layerCount">Количество слоев</param>
        /// <param name="layerNeuronsCount">Количество нейронов в слое</param>
        /// <param name="functions">Активационные функции</param>
        public FinancialNetwork(int inputCount, int layerCount, int layerNeuronsCount, List<FunctionTypes> functions)
        {
            FundsValue = 0;
            StockValue = 0;

            InputCountValue = inputCount;
            LayerCountValue = layerCount;
            NeuronInLayerValue = layerNeuronsCount;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            Transactions = new List<NetworkTransaction>();
            LayersList.Add(new Layer(1, inputCount, true, functions));

            for (var i = 1; i < layerCount; i++)
            {
                LayersList.Add(new Layer(i + 1, layerNeuronsCount, false, functions));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }
        }

        /// <summary>
        ///     Конструктор с количеством выходов
        /// </summary>
        /// <param name="inputCount">Количество входов</param>
        /// <param name="layerCount">Количество слоев</param>
        /// <param name="layerNeuronsCount">Количество нейронов в слоях</param>
        /// <param name="functions">Используемые активационные функции</param>
        /// <param name="outputCount">Количество выходов</param>
        public FinancialNetwork(int inputCount, int layerCount, int layerNeuronsCount, List<FunctionTypes> functions,
            int outputCount)
        {
            FundsValue = 0;
            StockValue = 0;

            InputCountValue = inputCount;
            LayerCountValue = layerCount;
            OutputCountValue = outputCount;
            NeuronInLayerValue = layerNeuronsCount;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            Transactions = new List<NetworkTransaction>();
            LayersList.Add(new Layer(1, inputCount, true, functions));

            //TODO: Оптимизировать цикл
            for (var i = 1; i < layerCount; i++)
            {
                LayersList.Add(new Layer(i + 1, layerNeuronsCount, false, functions));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }

            var lastLayer = new Layer(layerCount + 1, outputCount, true, functions);
            foreach (var inputNeuron in LayersList.Last().Neurons)
            {
                foreach (var outputNeuron in lastLayer.Neurons)
                {
                    SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                }
            }
            LayersList.Add(lastLayer);
        }

        /// <summary>
        ///     Конструктор для создания сети со случайными параметрами
        /// </summary>
        public FinancialNetwork()
        {
            FundsValue = 0;
            StockValue = 0;

            var randomizer = new Random();
            InputCountValue = randomizer.Next(50, 200);
            LayerCountValue = randomizer.Next(1, 10);
            NeuronInLayerValue = -1;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            LayersList.Add(new Layer(1, InputCountValue, true));

            for (var i = 1; i < LayerCountValue; i++)
            {
                var layerNeuronCount = randomizer.Next(2, 32);
                LayersList.Add(new Layer(i + 1, layerNeuronCount, false));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }
        }

        /// <summary>
        ///     Конструктор копирования
        /// </summary>
        /// <param name="source">Исходный экземпляр</param>
        public FinancialNetwork(FinancialNetwork source)
        {
            InputCountValue = source.InputCountValue;
            LayerCountValue = source.LayerCountValue;
            NeuronInLayerValue = source.NeuronInLayerValue;

            StockValue = source.StockValue;
            FundsValue = source.FundsValue;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            Transactions = new List<NetworkTransaction>();

            foreach (var layer in source.LayersList)
            {
                LayersList.Add(new Layer(layer));
            }

            foreach (var synapse in source.SynapsesList)
            {
                SynapsesList.Add(new Synapse(synapse));
            }

            foreach (var transaction in source.Transactions)
            {
                Transactions.Add(new NetworkTransaction(transaction));
            }
        }

        /// <summary>
        /// Конструктор на основе структуры
        /// </summary>
        /// <param name="sourceStructure">Структура сети</param>
        public FinancialNetwork(NetworkStructure sourceStructure)
        {
            FundsValue = 0;
            StockValue = 0;

            Random randomizer = new Random();
            InputCountValue = sourceStructure.LayerStructures.ElementAt(0).FunctionsList.Count();
            LayerCountValue  = sourceStructure.LayerStructures.Count();
            NeuronInLayerValue = sourceStructure.LayerStructures.ElementAt(1).FunctionsList.Count();
            
            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            Transactions = new List<NetworkTransaction>();
            
            for (int i = 0; i < sourceStructure.LayerStructures.Count(); i++)
            {
                var nextLayer = new Layer() { LayerId = i + 1 };
                for (int k = 0; k < sourceStructure.LayerStructures.ElementAt(0).FunctionsList.Count(); k++)
                {
                    nextLayer.Neurons.Add(new Neuron((FunctionTypes)sourceStructure.LayerStructures.ElementAt(0).FunctionsList[k]) { NeuronId = (i + 1) * 1000 + k });
                }
                LayersList.Add(nextLayer);
            }

            for (int i = 0; i < sourceStructure.SynapseStructures.Count(); i++)
            {
                var nextInputId = sourceStructure.SynapseStructures.ElementAt(i).InputNeuronId;
                var nextOutputId = sourceStructure.SynapseStructures.ElementAt(i).OutputNeuronId;

                var inputLayerIndex = nextInputId / 1000 - 1;
                var outputLayerIndex = nextOutputId / 1000 - 1;
                var inputNeuronIndex = nextInputId % 1000;
                var outputNeuronIndex = nextOutputId % 1000;

                Neuron inputNeuron = null, outputNeuron = null;

                if (LayersList.ElementAt(inputLayerIndex) != null)
                {
                    inputNeuron = LayersList.ElementAt(inputLayerIndex).Neurons.ElementAt(inputNeuronIndex);
                }

                if (LayersList.ElementAt(outputLayerIndex) != null)
                {
                    outputNeuron = LayersList.ElementAt(outputLayerIndex).Neurons.ElementAt(outputNeuronIndex);
                }

                if (inputNeuron == null)
                {
                    var randomLayer = randomizer.Next(0, LayersList.Count());
                    inputNeuron = LayersList.ElementAt(randomLayer).Neurons.ElementAt(randomizer.Next(0, LayersList.ElementAt(randomLayer).Neurons.Count()));
                }

                if (outputNeuron == null)
                {
                    var randomLayer = randomizer.Next(0, LayersList.Count());
                    outputNeuron = LayersList.ElementAt(randomLayer).Neurons.ElementAt(randomizer.Next(0, LayersList.ElementAt(randomLayer).Neurons.Count()));
                }

                SynapsesList.Add(new Synapse(randomizer.NextDouble() * (-0.1) + 0.05, inputNeuron, outputNeuron));
            }
        }

        public List<NetworkTransaction> Transactions { get; set; }

        /// <summary>
        ///     Граница покупки
        /// </summary>
        public double BuyBorder
        {
            get { return Convert.ToDouble(ConfigurationManager.AppSettings["buyBorder"].Replace(".", ",")); }
        }

        /// <summary>
        ///     Граница бездействия
        /// </summary>
        public double StandbyBorder
        {
            get { return Convert.ToDouble(ConfigurationManager.AppSettings["standbyBorder"].Replace(".", ",")); }
        }

        /// <summary>
        ///     Граница продажи
        /// </summary>
        public double SellBorder
        {
            get { return Convert.ToDouble(ConfigurationManager.AppSettings["sellBorder"].Replace(".", ",")); }
        }

        /// <summary>
        /// Капитал сети
        /// </summary>
        public double Funds
        {
            get { return FundsValue; }
            set { FundsValue = value; }
        }

        /// <summary>
        /// Акции сети
        /// </summary>
        public double Stock
        {
            get { return StockValue; }
            set { StockValue = value; }
        }

        /// <summary>
        ///     Продать все имеющиеся акции
        /// </summary>
        /// <param name="price">Текущая цена</param>
        /// <param name="date">Дата операции</param>
        public void SellStock(double price, DateTime date)
        {
            if (StockValue <= 0) return;
            FundsValue += Stock*price;
            StockValue = 0;
            Transactions.Add(new NetworkTransaction(true, price, Stock, date));
        }

        /// <summary>
        ///     Скупить максимально возможное количество акций
        /// </summary>
        /// <param name="price">Текущая цена</param>
        /// <param name="date">Текущая дата</param>
        public void BuyStock(double price, DateTime date)
        {
            if (FundsValue <= 0) return;
            StockValue += FundsValue/price;
            FundsValue = 0;
            Transactions.Add(new NetworkTransaction(false, price, (FundsValue/price), date));
        }
    }
}